﻿#region

using System;
using System.Diagnostics;

#endregion

namespace GGEditor.API.Utility
{
	[DebuggerStepThrough]
    public static class Contract
    {
        public static T NotNull<T>(T arg, string name) where T : class
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg == null)
                Helpers.ArgumentNull(name);
            return arg;
        }

        public static string NotEmpty(string arg, string name)
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg == null)
                Helpers.ArgumentNull(name);
            if (arg == string.Empty)
                Helpers.ArgumentInvalid(name, "cannot be an empty string.");
            return arg;
        }

        public static T NotEqual<T>(T arg, T value, string name)
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentNotNull(value, "item");

            if (value.Equals(arg))
                Helpers.ArgumentInvalid(name, "cannot be equal to {0}.", value.ToString());
            return arg;
        }

        public static int IsPositive(int arg, string name)
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg <= 0)
                Helpers.ArgumentInvalid(name, "must be greater than zero.");
            return arg;
        }

        public static T IsAtLeast<T>(T arg, T start, string name) where T : IComparable<T>
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg.CompareTo(start) < 0)
                Helpers.ArgumentInvalid(
                    name, string.Format("must be greater than or equal to {0}.", start));
            return arg;
        }

        public static T IsGreaterThan<T>(T arg, T lowerBound, string name) where T : IComparable<T>
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg.CompareTo(lowerBound) <= 0)
                Helpers.ArgumentInvalid(
                    name, string.Format("must be greater than {0}.", lowerBound));
            return arg;
        }

        public static T IsLessThan<T>(T arg, T upperBound, string name) where T : IComparable<T>
        {
            Helpers.ContractArgumentNameCheck(name);

            if (arg.CompareTo(upperBound) >= 0)
                Helpers.ArgumentInvalid(name, string.Format("must be less than {0}.", upperBound));
            return arg;
        }

        public static T IsInRange<T>(T arg, T start, T end, string name) where T : IComparable<T>
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentCheck(
                start.CompareTo(end) < 0, "end", "\"end\" must be strictly greater than \"start\".");

            if (arg.CompareTo(start) < 0 || arg.CompareTo(end) >= 0)
                Helpers.ArgumentOutOfRange(name, arg, start, end);
            return arg;
        }

        public static T IsTrue<T>(T arg, string name, string msg, bool predicate)
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentMsgCheck(msg);

            if (!predicate)
                Helpers.ArgumentInvalid(name, msg);
            return arg;
        }

        public static T IsFalse<T>(T arg, string name, string msg, bool predicate)
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentMsgCheck(msg);

            if (predicate)
                Helpers.ArgumentInvalid(name, msg);
            return arg;
        }

        public static T[] NoneNull<T>(T[] arg, string name) where T : class
        {
            Helpers.ContractArgumentNameCheck(name);

            NotNull(arg, name);
            for (int i = 0; i < arg.Length; i++) {
                if (arg[i] == null) {
                    string elementName = string.Format("{0}[{1}]", name, i);
                    Helpers.ArgumentNull(elementName);
                }
            }
            return arg;
        }

        public static T[] IsTrueForAll<T>(T[] arg, string name, string msg, Predicate<T> predicate)
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentMsgCheck(msg);
            Helpers.ContractArgumentNotNull(predicate, "predicate");

            NotNull(arg, name);
            for (int i = 0; i < arg.Length; i++) {
                if (!predicate(arg[i]))
                    Helpers.ArgumentInvalid(Helpers.ElementName(name, i));
            }
            return arg;
        }

        public static T[] IsFalseForAll<T>(T[] arg, string name, string msg, Predicate<T> predicate)
        {
            Helpers.ContractArgumentNameCheck(name);
            Helpers.ContractArgumentMsgCheck(msg);
            Helpers.ContractArgumentNotNull(predicate, "predicate");

            NotNull(arg, name);
            for (int i = 0; i < arg.Length; i++) {
                if (predicate(arg[i]))
                    Helpers.ArgumentInvalid(Helpers.ElementName(name, i));
            }
            return arg;
        }

		#region Helpers

		[DebuggerStepThrough]
		public static class Helpers
		{
			public static void ContractArgumentCheck(bool predicate, string name, string msg)
			{
				ContractArgumentNameCheck(name);
				ContractArgumentMsgCheck(msg);

				if (!predicate)
					throw new ContractException(
						string.Format("Contract argument \"{0}\" invalid: {1}", name, msg));
			}

			public static void ContractArgumentNotNull(object o, string name)
			{
				ContractArgumentCheck(o != null, name, "cannot be null!");
			}

			public static void ContractArgumentNotEmpty(string s, string name)
			{
				ContractArgumentNotNull(s, name);
				ContractArgumentCheck(s != string.Empty, name, "cannot be the empty string!");
			}

			public static void ContractArgumentNameCheck(string name)
			{
				if (name == null || name == string.Empty)
					throw new ContractException("argument name cannot be null or empty!");
			}

			public static void ContractArgumentMsgCheck(string msg)
			{
				if (msg == null || msg == string.Empty)
					throw new ContractException("error msg cannot be null or empty!");
			}

			public static void ArgumentNull(string name)
			{
				throw new ArgumentNullException(name);
			}

			public static void ArgumentOutOfRange(string name, string msg)
			{
				throw new ArgumentOutOfRangeException(name, msg);
			}

			public static void ArgumentOutOfRange<T>(string name, T arg, T start, T end)
			{
				string msg = string.Format("{0} <= X < {2}, got X = {1}", start, arg, end);
				throw new ArgumentOutOfRangeException(name, msg);
			}

			public static void ArgumentInvalid(string name)
			{
				string msg = string.Format("Argument \"{0}\" invalid.", name);
				throw new ArgumentException(msg);
			}

			public static void ArgumentInvalid(string name, string reason)
			{
				string msg = string.Format("Argument \"{0}\" invalid: {1}", name, reason);
				throw new ArgumentException(msg);
			}

			public static void ArgumentInvalid(
				string name, string reason, params object[] reasonFmtArgs)
			{
				ArgumentInvalid(name, string.Format(reason, reasonFmtArgs));
			}

			public static string ElementName(string name, int i)
			{
				return string.Format("{0}[{1}]", name, i);
			}
		}

		#endregion

    }
}